package org.xbdframework.boot.autoconfigure.transaction;

import java.util.*;
import java.util.stream.Collectors;
import javax.sql.DataSource;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.xbdframework.transaction.TransactionMethod;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.*;

/**
 * 统一自动化事务配置
 * <p>
 * 此事务配置为统一的事务配置
 *
 * @author luas
 * @since 1.5.0
 */
@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@ConditionalOnMissingBean(TransactionInterceptor.class)
@ConditionalOnBean(TransactionPointcutCustomizer.class)
public class TransactionAutoConfiguration {

	private static Logger logger = LoggerFactory
			.getLogger(TransactionAutoConfiguration.class);

	private final PlatformTransactionManager transactionManager;

	private final List<TransactionNameMatchCustomizer> transactionNameMatchCustomizers;

	private final TransactionPointcutCustomizer transactionPointcutCustomizer;

	TransactionAutoConfiguration(PlatformTransactionManager transactionManager,
			ObjectProvider<List<TransactionNameMatchCustomizer>> nameMatchCustomizers,
			ObjectProvider<TransactionPointcutCustomizer> transactionPointcutCustomizerObjectProvider) {
		this.transactionManager = transactionManager;
		this.transactionNameMatchCustomizers = Optional
				.ofNullable(nameMatchCustomizers.getIfAvailable())
				.orElse(Collections.emptyList());
		this.transactionPointcutCustomizer = transactionPointcutCustomizerObjectProvider.getIfUnique();
	}

	@Bean
	public TransactionInterceptor transactionAdvice() {
		DefaultTransactionAttribute REQUIRED = new DefaultTransactionAttribute();
		REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

		DefaultTransactionAttribute REQUIRED_READONLY = new DefaultTransactionAttribute();
		REQUIRED_READONLY
				.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
		REQUIRED_READONLY.setReadOnly(true);

		NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();

		source.addTransactionalMethod(TransactionMethod.SAVE.getValue(), REQUIRED);
		source.addTransactionalMethod(TransactionMethod.DELETE.getValue(), REQUIRED);
		source.addTransactionalMethod(TransactionMethod.UPDATE.getValue(), REQUIRED);
		source.addTransactionalMethod(TransactionMethod.CREATE.getValue(), REQUIRED);
		source.addTransactionalMethod(TransactionMethod.EXEC.getValue(), REQUIRED);
		source.addTransactionalMethod(TransactionMethod.GET.getValue(),
				REQUIRED_READONLY);
		source.addTransactionalMethod(TransactionMethod.SELECT.getValue(),
				REQUIRED_READONLY);
		source.addTransactionalMethod(TransactionMethod.QUERY.getValue(),
				REQUIRED_READONLY);
		source.addTransactionalMethod(TransactionMethod.FIND.getValue(),
				REQUIRED_READONLY);
		source.addTransactionalMethod(TransactionMethod.LIST.getValue(),
				REQUIRED_READONLY);
		source.addTransactionalMethod(TransactionMethod.COUNT.getValue(),
				REQUIRED_READONLY);
		source.addTransactionalMethod(TransactionMethod.IS.getValue(), REQUIRED_READONLY);
		source.addTransactionalMethod("*", REQUIRED_READONLY);

		customize(source);

		return new TransactionInterceptor(this.transactionManager, source);
	}

	@Bean
	public Advisor transactionAdvisor() {
		AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();

		customize(pointcut);

		return new DefaultPointcutAdvisor(pointcut, transactionAdvice());
	}

	private void customize(
			NameMatchTransactionAttributeSource nameMatchTransactionAttributeSource) {
		List<Map<String, TransactionAttribute>> transactionalMethods = this.transactionNameMatchCustomizers
				.stream()
				.map(transactionNameMatchCustomizer -> transactionNameMatchCustomizer
						.customize())
				.collect(Collectors.toList());

		List<Map<String, TransactionAttribute>> supportedTransactionalMethods = new ArrayList<>();

		transactionalMethods.stream().forEach(method -> method.forEach((k, v) -> {
			if (TransactionMethod.contains(k)) {
				logger.warn("事务拦截方法 {} 系统已定义，不允许重复定义！", k);
			}
			else {
				supportedTransactionalMethods.add(method);
			}
		}));

		supportedTransactionalMethods.stream()
				.forEach(transactionalMethod -> transactionalMethod.forEach(
						nameMatchTransactionAttributeSource::addTransactionalMethod));
	}

	private void customize(AspectJExpressionPointcut pointcut) {
		this.transactionPointcutCustomizer.customize(pointcut);
	}

}
